package org.spp.utilities;

import org.apache.commons.math.geometry.Vector3D;
import org.apache.commons.math.util.MathUtils;

/**
 * Point.java
 * @author Ilio Catallo, Eleonora Ciceri
 * @version 1.0
 * 
 * Description: class that implements the concept of point, both in tridimensional 
 *              and bidimensional case. It also provides some static methods to perform several
 *              common operations with vectors and points.
 * 
 * Date: Jul 10, 2011
 */
public abstract class Point {
    
    // Attributes
    protected double[] coordinates;    
    
    /**
     * Constructor for the class Point
     * @param coordinates 
     */
    public Point(double[] coordinates) {
        this.coordinates = coordinates;
    }
    
    /**
     * Constructor for the class Point
     * @param coordinates 
     */
    public Point(double[][] coordinates) {
        this.coordinates = new double[coordinates.length];
        
        for (int i = 0; i < coordinates.length; i++)
            this.coordinates[i] = coordinates[i][0];
    }    

    /**
     * Method that returns the coordinates of the point
     * @return coordinates
     */
    public double[] getCoordinates() {
        return coordinates;
    }
    
    /**
     * Method that verifies if two points are equal
     * @param secondPoint
     * @return isEqual
     */
    public boolean isEqual(Point secondPoint) {
        // Variables
        double[] secondPointCoordinates = secondPoint.getCoordinates();
        
        if (coordinates.length != secondPointCoordinates.length)
            return false;
        
        for (int i = 0; i < coordinates.length; i ++)
            if (Math.abs(secondPointCoordinates[i] - coordinates[i]) > 10*MathUtils.EPSILON)
                return false;
        
        return true;        
    }
    
    /**
     * Method that calculates the distance between two points
     * @param secondPoint
     * @return distance
     */
    public double computeDistance(Point secondPoint) {
        return MathUtils.distance(this.coordinates, secondPoint.getCoordinates());         
    }
    
    /**
     * Method that computes the dot product between two points
     * @param secondPoint
     * @return dotProduct
     */
    public double dotProduct(double[] secondPoint) {
        if (coordinates.length == 3){
            Vector3D firstPointVector = new Vector3D(coordinates[0], coordinates[1], coordinates[2]);
            Vector3D secondPointVector = new Vector3D(secondPoint[0], secondPoint[1], secondPoint[2]);

            return Vector3D.dotProduct(firstPointVector, secondPointVector); 
        }
        else if (coordinates.length == 2) {
            double result = coordinates[0]*secondPoint[0] + coordinates[1]*secondPoint[1];
            return result;
        }
        
        return 0;
    }
    
    /**
     * Method that computes the dot product between two points
     * @param secondPoint
     * @return dotProduct
     */
    public double dotProduct(Point secondPoint){
        return dotProduct(secondPoint.getCoordinates());
    }
    
    /**
     * Method that computes the dot product between two points
     * @param firstPoint
     * @param secondPoint
     * @return dotProduct
     */
    public static double dotProduct(double[] firstPoint, double[] secondPoint) {
        if (firstPoint.length == 3){
            Vector3D firstPointVector = new Vector3D(firstPoint[0], firstPoint[1], firstPoint[2]);
            Vector3D secondPointVector = new Vector3D(secondPoint[0], secondPoint[1], secondPoint[2]);

            return Vector3D.dotProduct(firstPointVector, secondPointVector); 
        }
        else if (firstPoint.length == 2) {
            double result = firstPoint[0]*secondPoint[0] + firstPoint[1]*secondPoint[1];
            return result;
        }
        
        return 0;        
    }
  
    /**
     * Normalize the coordiantes of the point
     */
    public void normalize(){
        if (coordinates.length == 3) {
            Vector3D point = new Vector3D(coordinates[0], coordinates[1], coordinates[2]);
            point = point.normalize();
            coordinates =  new double[]{point.getX(), point.getY(), point.getZ()};
        }
        else if (coordinates.length == 2) {
            double[] normalizedCoordinates = new double[2];
            normalizedCoordinates[0] = coordinates[0] / MathUtils.safeNorm(coordinates);
            normalizedCoordinates[1] = coordinates[1] / MathUtils.safeNorm(coordinates);
            coordinates = normalizedCoordinates;
        }
    }
    
    /**
     * Given the coordinates of a point, the method return them normalized
     * @param coordinates
     * @return normalizedCoordinates
     */
    public static double[] normalize(double[] coordinates){ 
        if (coordinates.length == 3) {
            Vector3D point = new Vector3D(coordinates[0], coordinates[1], coordinates[2]);
            point = point.normalize();
            return new double[]{point.getX(), point.getY(), point.getZ()};  
        }
        else if (coordinates.length == 2) {
            double[] normalizedCoordinates = new double[2];
            normalizedCoordinates[0] = coordinates[0] / MathUtils.safeNorm(coordinates);
            normalizedCoordinates[1] = coordinates[1] / MathUtils.safeNorm(coordinates);
            return normalizedCoordinates;
        }    
        return null;
    }
    
    /**
     * The method computes the cross product between two vectors
     * @param firstPointCoordinates
     * @param secondPointCoordinates
     * @return crossProduct
     */
    public static double[] crossProduct(double[] firstPointCoordinates, double[] secondPointCoordinates){
        Vector3D vector1 = new Vector3D(firstPointCoordinates[0], firstPointCoordinates[1], firstPointCoordinates[2]);
        Vector3D vector2 = new Vector3D(secondPointCoordinates[0], secondPointCoordinates[1], secondPointCoordinates[2]); 
        Vector3D result = Vector3D.crossProduct(vector1, vector2);
        
        return new double[]{result.getX(), result.getY(), result.getZ()};
    }
    
    /**
     * Method that computes the medium point between two points
     * @param secondPoint
     * @return mediumPoint
     */
    public double[] computeMediumPoint(Point secondPoint) {
        // Variables
        double[] mediumPoint = new double[coordinates.length];
        double[] secondPointCoordinates = secondPoint.getCoordinates();
        
        for (int i = 0; i < coordinates.length; i++)
            mediumPoint[i] = (coordinates[i] + secondPointCoordinates[i]) / 2;
        
        return mediumPoint;
    }
    
    /**
     * The method computes the vector between two points
     * @param secondPoint
     * @return 
     */
    public double[] computeVectorToPoint(Point secondPoint) {
        // Variables
        double[] normalVector = new double[coordinates.length];
        double[] secondPointCoordinates = secondPoint.getCoordinates();
        
        for (int i = 0; i < coordinates.length; i++)
            normalVector[i] = coordinates[i] - secondPointCoordinates[i];
        
        return normalVector;       
    }
    
}
